﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;

namespace Treasland.MassiveGridMap {
    public class NodeBaseEditor : EditorWindow {

        private List<Node> nodes;
        private List<Connection> connections;

        private GUIStyle nodeStyle;
        private GUIStyle selectedNodeStyle;
        private GUIStyle inPointStyle;
        private GUIStyle outPointStyle;

        private ConnectionPoint selectedInPoint;
        private ConnectionPoint selectedOutPoint;

        private Vector2 offset;
        private Vector2 drag;

        [MenuItem("Window/Node Base Editor")]
        private static void OpenWindow() {
            NodeBaseEditor window = GetWindow<NodeBaseEditor>();
            window.titleContent = new GUIContent("Node Base Editor");
        }


        private void OnEnable() {
            nodeStyle = new GUIStyle();
            nodeStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/node1.png") as Texture2D;
            nodeStyle.border = new RectOffset(12, 12, 12, 12);

            selectedNodeStyle = new GUIStyle();
            selectedNodeStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/node1 on.png") as Texture2D;
            selectedNodeStyle.border = new RectOffset(12, 12, 12, 12);

            inPointStyle = new GUIStyle();
            inPointStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn left.png") as Texture2D;
            inPointStyle.active.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn left on.png") as Texture2D;
            inPointStyle.border = new RectOffset(4, 4, 12, 12);

            outPointStyle = new GUIStyle();
            outPointStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn right.png") as Texture2D;
            outPointStyle.active.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn right on.png") as Texture2D;
            outPointStyle.border = new RectOffset(4, 4, 12, 12);
        }


        private void OnGUI() {
            //DrawGrid(20, 0.2f, Color.gray);
            //DrawGrid(100, 0.4f, Color.gray);

            DrawNodes();
            DrawConnections();

            DrawConnectionLine(Event.current);

            ProcessNodeEvent(Event.current);
            ProcessEvents(Event.current);

            if (GUI.changed) {
                Repaint();
            }
        }


        private void DrawGrid(float gridSpacing,float gridOpacity, Color gridColor) {
            int widthDivs = Mathf.CeilToInt(position.width / gridSpacing);
            int heightDivs = Mathf.CeilToInt(position.height / gridSpacing);
            Handles.BeginGUI();
            Handles.color = new Color(gridColor.r, gridColor.g, gridColor.b, gridOpacity);

            offset += drag * 0.5f;
            Vector3 newOffset = new Vector3(offset.x % gridSpacing, offset.y % gridSpacing, 0);
            for(int i = 0; i < widthDivs; ++i) {
                Handles.DrawLine(new Vector3(gridSpacing * i, -gridSpacing, 0) + newOffset,
                                 new Vector3(gridSpacing * i, position.height, 0f) + newOffset);

            }

            for(int j = 0; j < heightDivs; ++j) {
                Handles.DrawLine(new Vector3(-gridSpacing, gridSpacing * j, 0) + newOffset,
                                 new Vector3(position.width, gridSpacing * j, 0f) + newOffset);

                Handles.color = Color.white;
                Handles.EndGUI();
            }
        }


        private void DrawConnectionLine(Event e) {
            if(selectedInPoint != null && selectedOutPoint == null) {
                Handles.DrawBezier(
                    selectedInPoint.rect.center,
                    e.mousePosition,
                    selectedInPoint.rect.center + Vector2.left * 50f,
                    e.mousePosition - Vector2.left * 50f,
                    Color.white,
                    null,
                    3f);
                GUI.changed = true;
            }

            if(selectedOutPoint != null && selectedInPoint == null) {
                Handles.DrawBezier(
                    selectedOutPoint.rect.center,
                    e.mousePosition,
                    selectedOutPoint.rect.center - Vector2.left * 50f,
                    e.mousePosition + Vector2.left * 50f,
                    Color.white,
                    null,
                    3f);
                GUI.changed = true;
            }
        }



        private void DrawConnections() {
            if(connections != null) {
                for(int i = 0; i < connections.Count; ++i) {
                    connections[i].Draw();
                }
            }
        }

        private void DrawNodes() {
            if (nodes != null) {
                for (int i = 0; i < nodes.Count; ++i) {
                    nodes[i].Draw();
                }
            }
        }

        private void ProcessEvents(Event e) {
            switch (e.type) {
                case EventType.MouseDown: {
                        if(e.button == 0) {
                            ClearConnectionSelection();
                        }

                        if (e.button == 1) {
                            ProcessContextMenu(e.mousePosition);
                        }
                    }
                    break;
                case EventType.MouseDrag: {
                        if(e.button == 0) {
                            OnDrag(e.delta);                            
                        }

                    }
                    break;
            }
        }

        private void OnDrag(Vector2 delta) {
            drag = delta;
            if(nodes != null) {
                for(int i = 0; i < nodes.Count; ++i) {
                    nodes[i].Drag(delta);
                }
            }
            GUI.changed = true;

        }


        private void ProcessNodeEvent(Event e) {
            if(nodes != null) {
                for(int i = nodes.Count - 1; i >= 0; --i) {
                    bool guiChanged = nodes[i].ProcessEvents(e);
                    if (guiChanged) {
                        GUI.changed = true;
                    }
                }
            }
        }

        private void ProcessContextMenu(Vector2 mousePosition) {
            GenericMenu genericMenu = new GenericMenu();
            genericMenu.AddItem(new GUIContent("Add node"),
                                false,
                                () => OnClickAddNode(mousePosition));
            genericMenu.ShowAsContext();
        }

        private void OnClickAddNode(Vector2 mousePosition) {
            if (nodes == null) {
                nodes = new List<Node>();
            }

            nodes.Add(new Node(mousePosition, 200, 50, nodeStyle, selectedNodeStyle, inPointStyle,outPointStyle,OnClickInPoint,OnClickOutPoint, OnClickRemoveNode));
        }


        private void OnClickRemoveNode(Node node) {
            if(connections != null) {
                List<Connection> connectionsToRemove = new List<Connection>();
                for(int i = 0; i < connections.Count; ++i) {
                    if(connections[i].inPoint == node.inPoint || connections[i].outPoint == node.outPoint) {
                        connectionsToRemove.Add(connections[i]);
                    }
                }

                for(int i = 0; i < connectionsToRemove.Count; ++i) {
                    connections.Remove(connectionsToRemove[i]);
                }

                connectionsToRemove = null;
            }
            nodes.Remove(node);
        }


        private void OnClickInPoint(ConnectionPoint inPoint) {
            selectedInPoint = inPoint;
            if (selectedOutPoint != null) {
                if(selectedOutPoint.node != selectedInPoint.node) {
                    CreateConnection();
                    ClearConnectionSelection();
                } else {
                    ClearConnectionSelection();
                }
            }
        }

        private void OnClickOutPoint(ConnectionPoint outPoint) {
            selectedOutPoint = outPoint;
            if( selectedInPoint != null) {
                if(selectedOutPoint.node != selectedInPoint.node) {
                    CreateConnection();
                    ClearConnectionSelection();
                } else {
                    ClearConnectionSelection();
                }
            }
        }

        private void OnClickRemoveConnection(Connection connection) {
            connections.Remove(connection);
        }

        private void CreateConnection() {
            if(connections == null) {
                connections = new List<Connection>();
            }

            connections.Add(new Connection(selectedInPoint, selectedOutPoint, OnClickRemoveConnection));
        }


        private void ClearConnectionSelection() {
            selectedInPoint = null;
            selectedOutPoint = null;
        }
    }


    public class Node {
        public Rect rect;
        public string title;
        private bool isDragged;
        private bool isSelected;

        public ConnectionPoint inPoint;
        public ConnectionPoint outPoint;

        public GUIStyle style;
        public GUIStyle defaultNodeStyle;
        public GUIStyle selectedNodeStyle;

        public Action<Node> OnRemoveNode;

        public Node(Vector2 position, float width, float height, GUIStyle nodeStyle,
                    GUIStyle selectedStyle ,GUIStyle inPointStyle, GUIStyle outPointStyle,
                    Action<ConnectionPoint> OnClickInPoint,
                    Action<ConnectionPoint> OnClickOutPoint,
                    Action<Node> OnClickRemoveNode) {

            rect = new Rect(position.x, position.y, width, height);
            style = nodeStyle;
            inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint);
            outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint);

            defaultNodeStyle = nodeStyle;
            selectedNodeStyle = selectedStyle;
            OnRemoveNode = OnClickRemoveNode;
        }

        public void Drag(Vector2 delta) {
            rect.position += delta;
        }

        public void Draw() {
            inPoint.Draw();
            outPoint.Draw();
            GUI.Box(rect, title, style);
        }

        public bool ProcessEvents(Event e) {
            switch (e.type) {
                case EventType.MouseDown: {
                        if(e.button == 0) {
                            if (rect.Contains(e.mousePosition)) {
                                isDragged = true;
                                GUI.changed = true;
                                isSelected = true;
                                style = selectedNodeStyle;
                            } else {
                                GUI.changed = true;
                                isSelected = false;
                                style = defaultNodeStyle;
                            }
                        }

                        if(e.button == 1 && isSelected && rect.Contains(e.mousePosition)) {
                            ProcessContextMenu();
                            e.Use();
                        }
                    }
                    break;
                case EventType.mouseUp: {
                        isDragged = false;
                    }
                    break;
                case EventType.mouseDrag: {
                        if(e.button == 0 && isDragged) {
                            Drag(e.delta);
                            e.Use();
                            return true;
                        }
                    }
                    break;
            }
            return false;
        }

        private void ProcessContextMenu() {
            GenericMenu genericMenu = new GenericMenu();
            genericMenu.AddItem(new GUIContent("Remove node"), false, OnClickRemoveNode);
            genericMenu.ShowAsContext();
        }

        private void OnClickRemoveNode() {
            if(OnRemoveNode != null) {
                OnRemoveNode(this);
            }
        }

    }



    public enum ConnectionPointType {
        In,
        Out,
    }



    public class ConnectionPoint {
        public Rect rect;
        public ConnectionPointType type;
        public Node node;
        public GUIStyle style;
        public Action<ConnectionPoint> OnClickConnectionPoint;

        public ConnectionPoint(Node node, ConnectionPointType type, GUIStyle style, Action<ConnectionPoint> OnClickConnectionPoint) {
            this.node = node;
            this.type = type;
            this.style = style;
            this.OnClickConnectionPoint = OnClickConnectionPoint;
            rect = new Rect(0, 0, 10f, 20f);
        }

        public void Draw() {
            rect.y = node.rect.y + (node.rect.height * 0.5f) - rect.height * 0.5f;
            switch (type) {
                case ConnectionPointType.In: {
                        rect.x = node.rect.x - rect.width + 8f;
                    }
                    break;
                case ConnectionPointType.Out: {
                        rect.x = node.rect.x + node.rect.width - 8f;
                    }
                    break;
            }

            if (GUI.Button(rect, "", style)) {
                if(OnClickConnectionPoint != null) {
                    OnClickConnectionPoint(this);
                }
            }
        }
    }



    public class Connection {
        public ConnectionPoint inPoint;
        public ConnectionPoint outPoint;
        public Action<Connection> OnClickRemoveConnection;

        public Connection(ConnectionPoint inPoint, ConnectionPoint outPoint, Action<Connection> OnClickRemoveConnection) {
            this.inPoint = inPoint;
            this.outPoint = outPoint;
            this.OnClickRemoveConnection = OnClickRemoveConnection;
        }

        public void Draw() {
            Handles.DrawBezier(inPoint.rect.center,
                               outPoint.rect.center,
                               inPoint.rect.center + Vector2.left * 50f,
                               outPoint.rect.center - Vector2.left * 50f,
                               Color.white,
                               null, 2f);

            if(Handles.Button((inPoint.rect.center + outPoint.rect.center) * 0.5f,
                              Quaternion.identity,
                              4,
                              8,
                              Handles.RectangleCap)) {
                if(OnClickRemoveConnection != null) {
                    OnClickRemoveConnection(this);
                }
            }
        }
    }

}